#include <QtGui>
#include "glwidget.h"

#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "historywriter.h"
#include "C:\Program Files\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include\NIDAQmx.h"

using namespace std;

GLWidget::GLWidget(QWidget *parent, Grid *grid, ControlPanel *controlpanel, Grid *pdSquareIn)
    : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::NoSampleBuffers), parent, 0, Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::MSWindowsOwnDC)
{
    // initializations
    framenum = 0;
    srand(time(NULL));
    mothership = grid;
    pdSquare = pdSquareIn;
    cp = controlpanel;
    res = resolutionFromText(cp->ui.resolution->currentText());

    // NIDAQ initializations
    task = 0;
    const char *lines = cp->ui.triggerLine->text().toAscii();
    DAQmxCreateTask("", &task);
    DAQmxCreateDOChan(task, lines, "", DAQmx_Val_ChanPerLine);

    // Qt initializations
    QDesktopWidget *pDesktop = QApplication::desktop();
    QRect geometry = pDesktop->availableGeometry(1); //TODO: load this value from settings
    move(geometry.topLeft());

    setFixedSize(res[0], res[1]); // TODO: load this value from settings
    setAutoFillBackground(false);
    setAutoBufferSwap(false);

    updateGL();
}

void GLWidget::runStimuli()
{
    float color = cp->ui.contrast->text().toUInt(NULL, 16);

    int prestimFrames = cp->ui.prestimFrames->text().toInt();
    int stimFrames = cp->ui.stimFrames->text().toInt();
    int poststimFrames = cp->ui.poststimFrames->text().toInt();

    int totalFrames = (prestimFrames + stimFrames + poststimFrames);

    histwriter.saveHistory(*mothership, mothership->squares, prestimFrames, stimFrames, poststimFrames);

    // NI-DAQ stuff
    uInt8 data[] = {1};
    DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL,NULL);

    for (int frame = 0; frame < totalFrames; frame++)
    {
        // Blank the screen
        for (int sx = 0; sx < mothership->gridSize[0]; sx++)
        {
            for (int sy = 0; sy < mothership->gridSize[1]; sy++)
            {
                if (frame >= prestimFrames && frame < prestimFrames+stimFrames)
                {
                    mothership->colors[sx][sy] = mothership->squares[sx][sy] * color;
                    pdSquare->colors[0][0] = color;
                } else
                {
                    mothership->colors[sx][sy] = 0;
                    pdSquare->colors[0][0] = 0;
                }
            }
        }
        updateGL();
    }

    // Blank the screen
    for (int sx = 0; sx < mothership->gridSize[0]; sx++)
    {
        for (int sy = 0; sy < mothership->gridSize[1]; sy++)
        {
            mothership->colors[sx][sy] = 0;
        }
    }
    pdSquare->colors[0][0] = 0;
    updateGL();

    // Clear NI-DAQ stuff
    data[0] = 0;
    DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);

}

void GLWidget::runRandomSpots()
{
    int nStimuli = cp->ui.nStimuli->text().toUInt();
    int nSpots = cp->ui.nSpots->text().toUInt();
    float color = cp->ui.contrast->text().toUInt(NULL, 16);

    int prestimFrames = cp->ui.prestimFrames->text().toInt();
    int stimFrames = cp->ui.stimFrames->text().toInt();
    int poststimFrames = cp->ui.poststimFrames->text().toInt();


    int totalFrames = (prestimFrames + stimFrames + poststimFrames);

    // Set up the stimuli
    vector<int> numbers;
    vector< vector<int> > indices(nStimuli, vector<int>(nSpots));

    for (int stim = 0; stim < nStimuli; stim++)
    {
        // Generate array of numbers from [0..number of squares)
        for (int n = 0; n < (mothership->gridSize[0] * mothership->gridSize[1]); n++)
        {
            numbers.push_back(n);
        }

        // Pick nSpots indices from the numbers without replacement
        for (int i = 0; i < nSpots; i++)
        {
            int idx = rand() % numbers.size();
            indices[stim][i] = numbers[idx];
            numbers.erase(numbers.begin()+idx);
        }
    }

    histwriter.saveHistory(*mothership, indices, nSpots, prestimFrames, stimFrames, poststimFrames);

    // NI-DAQ stuff
    uInt8 data[] = {1};
    DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL,NULL);

    // Write to the Grid and update buffer
    for (int stim = 0; stim < nStimuli; stim++)
    {
        data[0] = 1;
        DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);
        for (int frame = 0; frame < totalFrames; frame++)
        {
            // Blank the screen during pre and post-stim
            for (int sx = 0; sx < mothership->gridSize[0]; sx++)
            {
                for (int sy = 0; sy < mothership->gridSize[1]; sy++)
                {
                    mothership->colors[sx][sy] = 0;
                }
            }
            pdSquare->colors[0][0] = 0;

            // Color the squares during stim
            if (frame >= prestimFrames && frame < prestimFrames+stimFrames)
            {
                // Convert the 1D indices into 2D indices and set the square color
                for (int i = 0; i < nSpots; i++)
                {
                    // Get quotient and remainder of the 1d-index divided by the # of columns
                    div_t ind2d;
                    ind2d = div(indices[stim][i], mothership->gridSize[0]);
                    // Assign this square to the chosen color
                    mothership->colors[ind2d.rem][ind2d.quot] = color;
                }

                pdSquare->colors[0][0] = color;
            }
            updateGL();
            QApplication::processEvents();
        }
        data[0] = 0;
        DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);
        Sleep(cp->ui.iti->text().toInt());
        QApplication::processEvents();
    }

    // Blank the screen
    for (int sx = 0; sx < mothership->gridSize[0]; sx++)
    {
        for (int sy = 0; sy < mothership->gridSize[1]; sy++)
        {
            mothership->colors[sx][sy] = 0;
        }
    }
    pdSquare->colors[0][0] = 0;
    updateGL();

    // Clear NI-DAQ stuff
    data[0] = 0;
    DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);

    //fclose(historyFile);
}

void GLWidget::runSequence()
{
    int nStimuli = mothership->gridSize[0]*mothership->gridSize[1];
    int nSpots = 1;
    float color = cp->ui.contrast->text().toUInt(NULL, 16);

    int prestimFrames = cp->ui.prestimFrames->text().toInt();
    int stimFrames = cp->ui.stimFrames->text().toInt();
    int poststimFrames = cp->ui.poststimFrames->text().toInt();


    int totalFrames = (prestimFrames + stimFrames + poststimFrames);

    // Set up the stimuli
    vector<int> numbers;
    vector< vector<int> > indices(nStimuli, vector<int>(nSpots));

    for (int stim = 0; stim < nStimuli; stim++)
    {
        indices[stim][0] = stim;
    }

    histwriter.saveHistory(*mothership, indices, nSpots, prestimFrames, stimFrames, poststimFrames);

    // NI-DAQ stuff
    uInt8 data[] = {1};
    DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL,NULL);

    // Write to the Grid and update buffer
    for (int stim = 0; stim < nStimuli; stim++)
    {
        data[0] = 1;
        DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);
        for (int frame = 0; frame < totalFrames; frame++)
        {
            // Blank the screen during pre and post-stim
            for (int sx = 0; sx < mothership->gridSize[0]; sx++)
            {
                for (int sy = 0; sy < mothership->gridSize[1]; sy++)
                {
                    mothership->colors[sx][sy] = 0;
                }
            }
            pdSquare->colors[0][0] = 0;

            // Color the squares during stim
            if (frame >= prestimFrames && frame < prestimFrames+stimFrames)
            {
                // Convert the 1D indices into 2D indices and set the square color
                for (int i = 0; i < nSpots; i++)
                {
                    // Get quotient and remainder of the 1d-index divided by the # of columns
                    div_t ind2d;
                    ind2d = div(indices[stim][i], mothership->gridSize[0]);
                    // Assign this square to the chosen color
                    mothership->colors[ind2d.rem][ind2d.quot] = color;
                }

                pdSquare->colors[0][0] = color;
            }
            updateGL();
            QApplication::processEvents();
        }
        data[0] = 0;
        DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);
        Sleep(cp->ui.iti->text().toInt());
        QApplication::processEvents();
    }

    // Blank the screen
    for (int sx = 0; sx < mothership->gridSize[0]; sx++)
    {
        for (int sy = 0; sy < mothership->gridSize[1]; sy++)
        {
            mothership->colors[sx][sy] = 0;
        }
    }
    pdSquare->colors[0][0] = 0;
    updateGL();

    // Clear NI-DAQ stuff
    data[0] = 0;
    DAQmxWriteDigitalLines(task,1,1,10.0,DAQmx_Val_GroupByChannel, data, NULL, NULL);

    //fclose(historyFile);
}

void GLWidget::displayNumChanged(int newDisplay)
{
    printf("newdisplay = %d\n", newDisplay); fflush(stdout);
    QDesktopWidget *pDesktop = QApplication::desktop();
    QRect geometry = pDesktop->availableGeometry(newDisplay);
    printf("Geom: %d %d %d %d\n", geometry.x(), geometry.y(), geometry.width(), geometry.height()); fflush(stdout);
    move(geometry.topLeft());
}

void GLWidget::resolutionChanged(QString text)
{
    res = resolutionFromText(text);
    setFixedSize(res[0], res[1]);

    glViewport(0, 0, res[0], res[1]);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0, res[0], res[1], 0, 1, -1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void GLWidget::triggerLineChanged()
{
    DAQmxClearTask(task);
    task = 0;
    const char *lines = cp->ui.triggerLine->text().toAscii();
    DAQmxCreateTask("", &task);
    DAQmxCreateDOChan(task, lines, "", DAQmx_Val_ChanPerLine);
}

void GLWidget::savePathChanged()
{
    histwriter.setSavePath(cp->ui.savePath->text());
}

void GLWidget::initializeGL()
{
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glViewport(0, 0, res[0], res[1]);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0, res[0], res[1], 0, 1, -1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBegin(GL_QUADS);

    for (int sx = 0; sx < mothership->gridSize[0]; sx++)
    {
        for (int sy = 0; sy < mothership->gridSize[1]; sy++)
        {
            GLfloat colorR = ((mothership->colors[sx][sy] & 0x00FF0000) >> 16) / 255.f;
            GLfloat colorG = ((mothership->colors[sx][sy] & 0x0000FF00) >> 8) / 255.f;
            GLfloat colorB = ( mothership->colors[sx][sy] & 0x000000FF) / 255.f;
            GLfloat colorA = ((mothership->colors[sx][sy] & 0xFF000000) >> 24) / 255.f;
            //printf("Color: %f %f %f %f \n", colorR, colorG, colorB, colorA); fflush(stdout);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(mothership->geometry[0] + sx * mothership->gridWidth),
                       float(mothership->geometry[1] + sy * mothership->gridWidth),
                       0);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(mothership->geometry[0] + sx * mothership->gridWidth),
                       float(mothership->geometry[1] + sy * mothership->gridWidth + mothership->gridWidth),
                       0);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(mothership->geometry[0] + sx * mothership->gridWidth + mothership->gridWidth),
                       float(mothership->geometry[1] + sy * mothership->gridWidth + mothership->gridWidth),
                       0);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(mothership->geometry[0] + sx * mothership->gridWidth + mothership->gridWidth),
                       float(mothership->geometry[1] + sy * mothership->gridWidth),
                       0);
        }
    }

    for (int sx = 0; sx < pdSquare->gridSize[0]; sx++)
    {
        for (int sy = 0; sy < pdSquare->gridSize[1]; sy++)
        {
            float colorR = ((pdSquare->colors[0][0] & 0x00FF0000) >> 16) / 255.f;
            float colorG = ((pdSquare->colors[0][0] & 0x0000FF00) >> 8) / 255.f;
            float colorB = ( pdSquare->colors[0][0] & 0x000000FF) / 255.f;
            float colorA = ((pdSquare->colors[0][0] & 0xFF000000) >> 24) / 255.f;

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(pdSquare->geometry[0] + sx * pdSquare->gridWidth),
                       float(pdSquare->geometry[1] + sy * pdSquare->gridWidth),
                       0);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(pdSquare->geometry[0] + sx * pdSquare->gridWidth),
                       float(pdSquare->geometry[1] + sy * pdSquare->gridWidth + pdSquare->gridWidth),
                       0);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(pdSquare->geometry[0] + sx * pdSquare->gridWidth + pdSquare->gridWidth),
                       float(pdSquare->geometry[1] + sy * pdSquare->gridWidth + pdSquare->gridWidth),
                       0);

            glColor4f(colorR, colorG, colorB, colorA);
            glVertex3f(float(pdSquare->geometry[0] + sx * pdSquare->gridWidth + pdSquare->gridWidth),
                       float(pdSquare->geometry[1] + sy * pdSquare->gridWidth),
                       0);
        }
    }

    glEnd();

    swapBuffers();
}

int* GLWidget::resolutionFromText(QString text)
{
    static int resolution[2];
    sscanf(text.toLocal8Bit().data(), "%dx%d", &resolution[0], &resolution[1]);

    return resolution;
}
